﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using Microsoft.Scripting.Hosting;
using System.Collections.Generic;
using Microsoft.Scripting.Hosting.Providers;
using Cjc.ExpressionEngine.Excel.Runtime;

namespace Cjc.ExpressionEngine
{
	public class Cell : INotifyPropertyChanged
	{
		public Formula Formula { get; private set; }

		private ScriptScope scope;
		private object value;
		private List<Cell> dependencies = null;

		public Cell( ScriptScope scope, Formula formula )
		{
			this.scope = scope;
			this.Formula = formula;
		}

		private void OnDependencyChanged( object sender, PropertyChangedEventArgs e )
		{
			Interlocked.Exchange<object>( ref value, null );

			if ( PropertyChanged != null ) PropertyChanged( this, e );
		}

		public object Value
		{
			get
			{
				if ( value == null )
				{
					Interlocked.CompareExchange<object>( ref value, GetValue(), null );

					if ( dependencies == null )
					{
						var worksheet = ( HostingHelpers.GetScope( scope ) as ExcelScope ).Worksheet;

						dependencies = ( from d in Formula.Dependencies
										 select worksheet.GetCell( d ) ).ToList();

						foreach ( var c in dependencies )
						{
							c.PropertyChanged += OnDependencyChanged;
						}
					}
				}

				return value;
			}

			set
			{
				if ( value is string && ( (string)value ).Trim().StartsWith( "=" ) )
				{
					Interlocked.Exchange<object>( ref this.value, null );

					this.Formula.ExpressionText = value.ToString();
				}
				else
				{
					Interlocked.Exchange<object>( ref this.value, value );
				}

				OnChanged();
			}
		}

		private void OnDependencyChanged()
		{
			Interlocked.Exchange<object>( ref this.value, null );
			OnChanged();
		}

		private void OnChanged()
		{
			if ( PropertyChanged != null ) PropertyChanged( this, new PropertyChangedEventArgs( "Value" ) );
		}

		public object GetValue()
		{
			return Formula.Execute( scope );
		}

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}
}